﻿Imports System.Text.RegularExpressions
Imports System.IO
Imports System.Web
Imports System.Xml
Imports System.Xml.XPath
Imports System.Collections.Generic
Imports System.Text
Imports System.Web.Profile
Imports System.Configuration
Imports System.Configuration.Provider


Class XmlProfileProvider
    Inherits ProfileProvider
#Region "Properties"
    Private _AppName As String
    Public Overrides Property ApplicationName() As String
        Get
            Return _AppName
        End Get
        Set(ByVal value As String)
            If value.Length > 256 Then
                Throw New ProviderException("ApplicationName too long - cannot exceed 256 characters in length")
            End If

            _AppName = value
        End Set
    End Property

    Private _ProfileFolder As String = "~/App_Data/XmlProfiles/"
    ' default to ~/App_Data/XmlProfiles/
    Public Property ProfileFolder() As String
        Get
            Return _ProfileFolder
        End Get
        Set(ByVal value As String)
            _ProfileFolder = value
        End Set
    End Property
#End Region

#Region "Initialize Method"
    Public Overrides Sub Initialize(ByVal name As String, ByVal config As System.Collections.Specialized.NameValueCollection)
        MyBase.Initialize(name, config)

        ApplicationName = config("applicationName")
        If Not String.IsNullOrEmpty(config("profileFolder")) Then
            ProfileFolder = config("profileFolder")
        End If

        If String.IsNullOrEmpty(ApplicationName) Then
            Throw New ProviderException("You _must_ provide the 'applicationName' setting when using the XmlProfileProvider")
        End If

        ' Make sure that there are no unknown settings
        config.Remove("applicationName")
        config.Remove("profileFolder")

        If config.Count > 0 Then
            Throw New ProviderException("Unrecognized attribute: " & config.GetKey(0))
        End If
    End Sub
   
#End Region

#Region "Get and Set Properties Methods"
    Public Overrides Function GetPropertyValues(ByVal context As System.Configuration.SettingsContext, ByVal collection As System.Configuration.SettingsPropertyCollection) As System.Configuration.SettingsPropertyValueCollection
        Dim settings As New SettingsPropertyValueCollection()

        ' Make sure we have an entry for this username in the XML data
        Dim username As String = TryCast(context("UserName"), String)
        If Not String.IsNullOrEmpty(username) Then
            ' Make sure username doesn't contain .. in the username (like '../SomeOtherUser/')
            If username.IndexOf("..") >= 0 Then
                Throw New ProviderException("Cannot access profile data for users with a username that contains '..'")
            End If

            ' Get the profile values for the user
            Dim usersProperties As Dictionary(Of String, Object) = GetUserProfile(username)

            For Each [property] As SettingsProperty In collection
                ' Indicate that provider-specific serialized properties should be
                ' serialized as strings for primitive types and as XML for non-primitive types
                If [property].SerializeAs = SettingsSerializeAs.ProviderSpecific Then
                    If [property].PropertyType.IsPrimitive OrElse [property].PropertyType Is GetType([String]) Then
                        [property].SerializeAs = SettingsSerializeAs.[String]
                    Else
                        [property].SerializeAs = SettingsSerializeAs.Xml
                    End If
                End If

                ' Create a new SettingsPropertyValue based on the current SettingsProperty object
                Dim setting As New SettingsPropertyValue([property])

                If usersProperties IsNot Nothing Then
                    setting.IsDirty = False

                    If usersProperties.ContainsKey([property].Name) Then
                        setting.SerializedValue = usersProperties([property].Name)
                        setting.Deserialized = False
                    End If
                End If

                ' Add the settings value to the collection
                settings.Add(setting)

            Next
        End If

        Return settings
        ' Return the settings collection
    End Function

    Protected Overridable Function GetUserProfile(ByVal username As String) As Dictionary(Of String, Object)
        ' Open the XML file            
        If Not File.Exists(GetProfileFilePath(username)) Then
            Return Nothing
        Else
            ' file doesn't exist!
            Dim propertyValues As New Dictionary(Of String, Object)()

            Dim xmlDoc As New XmlDocument()
            xmlDoc.Load(GetProfileFilePath(username))
            Dim rootNode As XmlNode = xmlDoc.DocumentElement

            For Each xmlProperty As XmlNode In rootNode.ChildNodes
                If xmlProperty.InnerXml.StartsWith("<![CDATA") Then
                    propertyValues.Add(xmlProperty.Name, Convert.FromBase64String(xmlProperty.InnerText))
                Else
                    propertyValues.Add(xmlProperty.Name, xmlProperty.InnerXml)
                End If
            Next

            Return propertyValues
        End If
    End Function


    Public Overrides Sub SetPropertyValues(ByVal context As System.Configuration.SettingsContext, ByVal collection As System.Configuration.SettingsPropertyValueCollection)
        Dim username As String = TryCast(context("UserName"), String)
        Dim userIsAuthenticated As Boolean = CBool(context("IsAuthenticated"))

        ' If no username is specified, or if no properties are to be saved, exit
        If String.IsNullOrEmpty(username) OrElse collection.Count = 0 Then
            Return
        End If

        ' If ALL properties are non-dirty, then exit
        If Not ExistsDirtyProperty(collection) Then
            Return
        End If

        ' Otherwise, save the entire set of data, regardless of IsDirty.
        ' That is, rather than trying to piecemeal together the changes into an existing XML file,
        ' we're going to blow away the XML file (if it already exists) and just build it up based on the
        ' values passed in...
        Using sw As New StreamWriter(GetProfileFilePath(username), False)
            Using writer As New XmlTextWriter(sw)
                writer.Formatting = Formatting.Indented

                writer.WriteStartDocument()
                writer.WriteStartElement("profileData")

                For Each setting As SettingsPropertyValue In collection
                    ' If the user is not authenticated and the property does not allow anonymous access, skip serializing it
                    If Not userIsAuthenticated AndAlso Not CBool(setting.[Property].Attributes("AllowAnonymous")) Then
                        Continue For
                    End If

                    ' Skip the current property if it's not dirty and is currently assigned its default value
                    If Not setting.IsDirty AndAlso setting.UsingDefaultValue Then
                        Continue For
                    End If

                    ' Serialize data based on property's SerializeAs type
                    If setting.[Property].SerializeAs = SettingsSerializeAs.[String] Then
                        writer.WriteElementString(setting.Name, Convert.ToString(setting.SerializedValue))
                    ElseIf setting.[Property].SerializeAs = SettingsSerializeAs.Xml Then
                        ' strip out the <?xml ... ?> portion from the serialized string
                        Dim xmlData As String = TryCast(setting.SerializedValue, String)
                        xmlData = Regex.Replace(xmlData, "^<\?xml .*?\?>", String.Empty)

                        writer.WriteRaw(String.Format("<{0}>{1}</{0}>", setting.Name, xmlData))
                    ElseIf setting.[Property].SerializeAs = SettingsSerializeAs.Binary Then
                        ' encode the binary data using base64 encoding
                        Dim encodedBinaryData As String = Convert.ToBase64String(TryCast(setting.SerializedValue, Byte()))
                        writer.WriteStartElement(setting.Name)
                        writer.WriteCData(encodedBinaryData)
                        writer.WriteEndElement()
                    Else
                        ' unknown serialize type!
                        Throw New ProviderException(String.Format("Invalid value for SerializeAs; expected String, Xml, or Binary, received {0}", System.[Enum].GetName(setting.[Property].SerializeAs.[GetType](), setting.[Property].SerializeAs)))
                    End If
                Next

                writer.WriteEndElement()
                writer.WriteEndDocument()

                writer.Close()
            End Using

            sw.Close()
        End Using
    End Sub

    Protected Overridable Function GetProfileFilePath(ByVal username As String) As String
        Return Path.Combine(HttpContext.Current.Server.MapPath(ProfileFolder), String.Format("{0}\{1}.xml", ApplicationName, username))
    End Function

    Protected Overridable Function ExistsDirtyProperty(ByVal collection As System.Configuration.SettingsPropertyValueCollection) As Boolean
        For Each setting As SettingsPropertyValue In collection
            If setting.IsDirty Then
                Return True
            End If
        Next

        ' If we reach here, none are dirty
        Return False
    End Function
#End Region

    Public Overrides Function DeleteInactiveProfiles(ByVal authenticationOption As ProfileAuthenticationOption, ByVal userInactiveSinceDate As DateTime) As Integer
        Throw New Exception("The method or operation is not implemented.")
    End Function

    Public Overrides Function DeleteProfiles(ByVal usernames As String()) As Integer
        Throw New Exception("The method or operation is not implemented.")
    End Function

    Public Overrides Function DeleteProfiles(ByVal profiles As ProfileInfoCollection) As Integer
        Throw New Exception("The method or operation is not implemented.")
    End Function

    Public Overrides Function FindInactiveProfilesByUserName(ByVal authenticationOption As ProfileAuthenticationOption, ByVal usernameToMatch As String, ByVal userInactiveSinceDate As DateTime, ByVal pageIndex As Integer, ByVal pageSize As Integer, ByRef totalRecords As Integer) As ProfileInfoCollection
        Throw New Exception("The method or operation is not implemented.")
    End Function

    Public Overrides Function FindProfilesByUserName(ByVal authenticationOption As ProfileAuthenticationOption, ByVal usernameToMatch As String, ByVal pageIndex As Integer, ByVal pageSize As Integer, ByRef totalRecords As Integer) As ProfileInfoCollection
        Throw New Exception("The method or operation is not implemented.")
    End Function

    Public Overrides Function GetAllInactiveProfiles(ByVal authenticationOption As ProfileAuthenticationOption, ByVal userInactiveSinceDate As DateTime, ByVal pageIndex As Integer, ByVal pageSize As Integer, ByRef totalRecords As Integer) As ProfileInfoCollection
        Throw New Exception("The method or operation is not implemented.")
    End Function

    Public Overrides Function GetAllProfiles(ByVal authenticationOption As ProfileAuthenticationOption, ByVal pageIndex As Integer, ByVal pageSize As Integer, ByRef totalRecords As Integer) As ProfileInfoCollection
        Throw New Exception("The method or operation is not implemented.")
    End Function

    Public Overrides Function GetNumberOfInactiveProfiles(ByVal authenticationOption As ProfileAuthenticationOption, ByVal userInactiveSinceDate As DateTime) As Integer
        Throw New Exception("The method or operation is not implemented.")
    End Function
End Class
